package com.example.controller;

import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.impl.HistoricVariableInstanceQueryProperty;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.query.QueryProperty;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@RestController
@RequestMapping("act")
public class ActController {

    @Resource
    private RuntimeService runtimeService;

    @Resource
    private TaskService taskService;

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private HistoryService historyService;

    private final DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();

    private final Supplier<String> CURRENT_USER = () -> SecurityContextHolder.getContext().getAuthentication().getName();

    @GetMapping("username")
    public String username() {
        return CURRENT_USER.get();
    }

    @PostMapping("deployment/deployByFile")
    public Object deployByFile(@RequestPart MultipartFile bpmn) throws IOException {
        return repositoryService.createDeployment()
            .addInputStream(bpmn.getOriginalFilename(), bpmn.getInputStream())
            .name(bpmn.getName())
            .deploy();
    }

    @PostMapping("deployment/deployByXml")
    public Object deployByXml(String xml) {
        return repositoryService.createDeployment()
                .addString(UUID.randomUUID().toString() + ".bpmn20.xml", xml)
                .deploy();
    }

    @PostMapping("deployment/deleteById/{deploymentId}")
    public String deleteDeployment(@PathVariable String deploymentId) {
        repositoryService.deleteDeployment(deploymentId, true);
        return "ok";
    }

    @PostMapping("process/startById/{processId}")
    public String startProcess(@PathVariable String processId, @RequestParam Map<String, Object> vars) {
        Authentication.setAuthenticatedUserId(CURRENT_USER.get());
        runtimeService.startProcessInstanceById(processId, vars);
        return "ok";
    }

    @GetMapping("process/getXmlById/{processId}")
    public String getProcessXml(@PathVariable String processId) throws IOException {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processId).singleResult();
        InputStream xml = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
        return FileCopyUtils.copyToString(new InputStreamReader(xml));
    }

    @GetMapping("process/list")
    public Object processList() {
        return repositoryService.createProcessDefinitionQuery().list().stream()
                .map(pro -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("id", pro.getId());
                    map.put("name", pro.getName());
                    map.put("deploymentId", pro.getDeploymentId());
                    Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(pro.getDeploymentId()).singleResult();
                    map.put("deploymentTime", deployment.getDeploymentTime());
                    return map;
                }).sorted(Comparator.comparing(a -> a.get("deploymentTime").toString())).collect(Collectors.toList());
    }

    @GetMapping("process/getInstanceDiagram/{instanceId}")
    public void getInstanceDiagram(@PathVariable String instanceId, HttpServletResponse response) throws IOException {
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(instanceId).singleResult();
        if (processInstance == null) {
            throw new RuntimeException("instanceId not exists");
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        List<String> highLightActivities = runtimeService.createExecutionQuery().processInstanceId(instanceId).list()
                .stream().map(Execution::getActivityId).collect(Collectors.toList());
        List<String> highLightFlows = new ArrayList<>();

        InputStream inputStream = diagramGenerator.generateDiagram(bpmnModel, highLightActivities, highLightFlows, "黑体", "黑体", "黑体");
        FileCopyUtils.copy(inputStream, response.getOutputStream());
    }

    @GetMapping("process/getInstanceVariable/{instanceId}")
    public Object getInstanceVariable(@PathVariable String instanceId) {
        Map<String, HistoricTaskInstance> taskMap = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(instanceId)
                .list().stream().collect(Collectors.toMap(HistoricTaskInstance::getId, t -> t));
        Set<String> taskIds = taskMap.keySet();
        Map<String, Map<String, Object>> map = historyService.createHistoricVariableInstanceQuery()
                .taskIds(taskIds).list().stream()
                .collect(Collectors.groupingBy(HistoricVariableInstance::getTaskId,
                        Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue, (k1, k2) -> k2, LinkedHashMap::new)));
        for (String taskId : taskIds) {
            Map<String, Object> var = map.computeIfAbsent(taskId, k -> new HashMap<>());
            HistoricTaskInstance task = taskMap.get(taskId);
            if (task != null) {
                var.put("assignee", task.getAssignee());
                var.put("endTime", task.getEndTime());
                var.put("name", task.getName());
            }
        }
        return map.values().stream().filter(a -> a.get("endTime") != null)
                .sorted(Comparator.comparing(a -> (Date) a.get("endTime"), Comparator.reverseOrder()))
                .collect(Collectors.toList());
    }

    @GetMapping("process/myApply")
    public Object getMyProcess() {
        return historyService.createHistoricProcessInstanceQuery()
                .startedBy(CURRENT_USER.get())
                .orderByProcessInstanceStartTime().desc()
                .list().stream()
                .map(p -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("id", p.getId());
                    map.put("name", p.getProcessDefinitionName());
                    map.put("processId", p.getProcessDefinitionId());
                    map.put("startTime", p.getStartTime());
                    map.put("endTime", p.getEndTime());
                    return map;
                }).collect(Collectors.toList());
    }

    @PostMapping("task/complete/{taskId}")
    public Object completeTask(@PathVariable String taskId, @RequestParam Map<String, Object> vars) {
        taskService.setVariablesLocal(taskId, vars);
        taskService.complete(taskId, vars);
        return "ok";
    }

    @GetMapping("task/list")
    public Object taskList() {
        return taskService.createTaskQuery()
                .orderByTaskCreateTime().desc()
                .list().stream()
                .map(task -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("id", task.getId());
                    map.put("name", task.getName());
                    map.put("processId", task.getProcessDefinitionId());
                    map.put("variables", taskService.getVariables(task.getId()));
                    map.put("assignee", task.getAssignee());
                    map.put("createTime", task.getCreateTime());
                    return map;
                }).collect(Collectors.toList());
    }

    @GetMapping("task/history")
    public Object taskHistory() {
        return historyService.createHistoricTaskInstanceQuery()
                .finished().orderByHistoricTaskInstanceEndTime().desc()
                .list().stream()
                .map(task -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("id", task.getId());
                    map.put("name", task.getName());
                    map.put("processId", task.getProcessDefinitionId());
                    map.put("variables", historyService.createHistoricVariableInstanceQuery().taskId(task.getId()).list().stream()
                            .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue, (k1 , k2) -> k1)));
                    map.put("assignee", task.getAssignee());
                    map.put("endTime", task.getEndTime());
                    return map;
        }).collect(Collectors.toList());
    }
}
